JOISC 2023 P1.1 Two Currencies¶
Problem¶
Problem Link¶
https://www.acmicpc.net/problem/27992
https://oj.uz/problem/view/JOI23_currencies
Summary¶
정점 \(N\)개의 트리가 주어지고, \(M\)개의 간선은 지나려면 통행료를 내야 한다.
\(P_i\)번째 간선을 통과하기 위해서는 \(1\)개의 금화를 내거나, \(C_i\)개의 은화를 내야 한다. \((1 \leq i \leq M)\)
\(Q\)개의 쿼리가 주어지며, \(i\)번 쿼리에서는 \(S_i\)번 정점에서 \(T_i\)번 정점으로 금화 \(X_i\)개, 은화 \(Y_i\)개를 갖고 이동하려 한다. \((1 \leq i \leq Q)\)
이 때, 이동이 가능한지 판별하고, 가능하다면 남는 금화의 개수의 최댓값을 구하여라.
Constraints¶
- \(2 \leq N \leq 100,000\)
- \(1 \leq M \leq 100,000\)
- \(1 \leq Q \leq 100,000\)
- \(1 \leq P_j \leq N-1\), \(1 \leq C_j \leq 10^9\) \((1 \leq j \leq M)\)
- \(1 \leq S_k, T_k \leq N\), \(0 \leq X_k \leq 10^9\), \(0 \leq Y_k \leq 10^{18}\) \((1 \leq k \leq Q)\)
Solution¶
Subtask 1¶
- \(N \leq 2000\), \(M \leq 2000\), \(Q \leq 2000\)
한 쿼리에서 이동하는 경로에 있는 모든 통행료들을 모아서 생각하자. 통행료들의 배열 \(C_i\)는 금화 \(1\)개를 내거나, 은화 \(C_i\)개를 내야 함을 의미한다. 사용할 수 있는 은화의 총 개수가 정해진 상태에서, 금화를 최대한 적게 사용하는 것이 목적이니, \(C_i\)가 작은 통행료부터 예산이 바닥날 때까지 은화로 지불하고, 나머지를 금화로 지불하는 것이 최선이다.
Observation 1
한 쿼리 \(k\)에서 경로에 있는 모든 통행료들을 \(C_i\)의 순서대로 정렬한 후, \(C_i\)가 작은 통행료부터 순서대로 합이 \(Y_k\)이하일 때까지 최대한 더하고 남은 통행료들을 금화로 지불하는 것이 최적해이다.
Observation 1을 이용하여 Naive하게 각 쿼리마다 경로를 따라 모든 통행료들의 배열을 얻고, 정렬하여 답을 계산하면 \(O(Q(N+M))\)에 문제를 해결할 수 있다.
CheckPoint
Observation 1을 이용하여 Naive하게 구현하면 \(O(Q(N+M))\)에 문제를 해결할 수 있다.
Complexity
Subtask 4 (Full)¶
\(N\), \(Q\), \(M\)이 클 때에는 직접 \(C_i\)들을 정렬하여 답을 구할 수 없다. 직접 정렬하지 않고, Observation 1의, \(C_i\)가 작은 값들부터 이용해야 한다는 조건을 어떻게 활용하면 좋을까?
이분탐색을 통해 해결할 수 있다.
우선, 편의를 위하여 모든 \(C_i\)가 서로 다르다고 가정한다.
\(C_i \leq T\)인 통행료들의 합을 구하여 통행료들의 합이 \(Y_k\)보다 작거나 같은 \(T\)의 최댓값을 구하면, \(C_i \leq T\)인 통행료들의 개수가 곧 은화로 지불할 수 있는 통행료들의 수를 의미한다.
이제 경로 \((S_k, T_k)\) 에 있는 통행료들 중 \(T\) 이하인 것들의 합, 혹은 개수에 대한 쿼리에 빠른 시간 안에 답할 수 있다면, 이 결과를 이용하여 이분탐색을 통해 답을 구할 수 있다.
CheckPoint
경로 \((S_k, T_k)\) 에 있는 통행료들 중 \(T\) 이하인 것들의 합, 혹은 개수에 대한 쿼리에 빠른 시간 안에 답할 수 있다고 가정하자. \(C_i \leq T\)인 통행료들의 합이 \(Y_k\)보다 작거나 같은 \(T\)의 최댓값을 이분탐색으로 구하여 문제를 해결할 수 있다.
루트에서 임의의 정점까지의 경로에서 \(T\)이하인 수들의 개수나 합은 Persistent Segment Tree를 활용하여 구할 수 있다. 트리에서 DFS를 돌며, 자식의 PST는 부모의 PST에 간선이 지나는 \(C_i\)를 추가한 형태로 PST를 구성한 후, 경로 \((S_k, T_k)\)의 쿼리는 \(S_k\), \(T_k\), \(lca\) 에서의 \(T\)에 대한 쿼리를 날려 구할 수 있다. 이분탐색에 \(O(logY)\), PST 쿼리에서 \(O(\log M)\)의 시간이 걸리니, 전체 \(O(N\log N+M+QlogY\log M)\)에 문제를 해결할 수 있다.
CheckPoint
필요한 쿼리는 트리에서의 Persistent Segment Tree를 이용하여 구할 수 있다.
전체 시간복잡도는 \(O(N\log N+M+QlogY\log M)\)이다.
Complexity
모든 쿼리가 오프라인임을 이용하면 상수가 큰 PST를 사용하지 않고도 문제를 해결할 수 있다. \(Q\)번의 이분탐색을 해야 하니, Parallel Binary Search를 사용하자.
모든 쿼리에 대하여 동시에 이분탐색을 하고, 한 번 이분탐색을 할 때 \(T\)와 \(C_i\)들을 정렬한다. 정렬된 순서대로 트리에 간선 가중치 업데이트, 경로의 가중치 합을 구할 수 있어야 한다. 이는 Euler Tree Trick을 사용해서 트리를 일직선으로 핀 후, 구간 업데이트, 점 쿼리로 해결할 수 있으니 Segment Tree (Lazy Propagation 필요 없음)으로 구한다. 이분탐색에 총 \(O(logY)\), 정렬 및 Segment Tree 쿼리에 \(O(\log N)\)의 시간이 걸리니, 전체 \(O(N\log N+M+QlogY\log N)\)에 문제를 해결할 수 있다.
CheckPoint
오프라인 쿼리이니, Parallel Binary Search를 사용하면 간선 가중치 업데이트와 경로 가중치 합 쿼리 문제로 환원할 수 있다.
이는 Euler Tree Trick으로 트리를 일직선으로 피고, Segment Tree를 활용하여 해결할 수 있다.
전체 시간복잡도는 \(O(N\log N+M+QlogY\log N)\)이다.
Complexity
마지막으로, 모든 \(C_i\)가 다르다고 가정하였는데, \(C_i\)가 같을 수 있다면 같은 \(C_i\)중 일부만 사용하는 경우를 고려할 수 없다. 이는 단순히 임의로 tie-breaking rule을 적용한 후, 이분탐색을 정렬된 \(C_i\)들의 배열에 대해서 시행함으로서 해결할 수 있다.
Code¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
|